package com.grm.aspect;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.grm.annotation.Log;
import com.grm.entity.SysLog;
import com.grm.service.LogService;
import com.grm.util.AjaxUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;

/**
 * desc: 日志切面类
 *
 * @author gaorimao
 * @since 2021-3-14
 */
@Aspect
@Component
public class LogAspect {

    private Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Autowired
    private LogService logService;
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private ObjectMapper objectMapper;
    
  //Service层切点
    @Pointcut("execution (* com.grm.service.impl..*.*(..))")
    public  void serviceAspect() {
    }
	
	 /**
     * desc: 异常通知 用于拦截service层记录异常日志
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
    public  void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		//todo: Log入库
 
    }

    /**
     * desc: 自定义切点表达式，检测自定义注解@Log,凡是使用@Log的地方都会自动进入aop切面
     *
     */
    @Pointcut("@annotation(com.grm.annotation.Log)")
    public void pointcut() {}

    /**
     * desc: 前置通知，记录controller层操作
     *
     * @param joinPoint 切入点
     */
    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) {
    	try {
			handleLog(joinPoint);
		} catch (Exception e) {
            logger.error("[LogAspect] doBefore error = {}",e);
		}
    }

    /**
     * desc: 通过反射获取日志信息，存入数据库
     *
     * @param joinPoint 切点
     * @throws Exception Exception
     */
    private void handleLog(final JoinPoint joinPoint) throws Exception{
        // 获得注解
        Method method = getMethod(joinPoint);
        Log log = getAnnotationLog(method);
        if (log == null) {
            return;
        }
        SysLog sysLog = setSysLogInfo(joinPoint,method,log);
        logService.insert(sysLog);
    }
    
    /**
     * desc: 初始化日志信息，存入数据库
     *
     * @return SysLog
     */
    private SysLog setSysLogInfo(JoinPoint joinPoint,Method method,Log log) {
    	// 操作数据库日志表
        SysLog sysLog = new SysLog();
        //获取当前登陆人，获取操作人id,此处不写获取当前登录人的逻辑，直接写死operatorId=1
        sysLog.setOperatorId(1L);
        // 请求信息
        sysLog.setType(AjaxUtils.isAjax(request) ? 0 : 1);
        sysLog.setAction(log.value());
        sysLog.setHost(request.getRemoteHost());
        sysLog.setUri(request.getRequestURI().toString());
        sysLog.setHttpMethod(request.getMethod());
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        sysLog.setClassMethod(classMethod);
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = discoverer.getParameterNames(method);
        if (args != null && paramNames != null) {
            StringBuilder params = new StringBuilder();
            try {
				params = handleParams(params, args, Arrays.asList(paramNames));
			} catch (JsonProcessingException e) {
				logger.error("[LogAspect] setSysLogInfo handleParams error = {}",e);
			}
            sysLog.setParams(params.toString());
        }
        return sysLog;
    }

    /**
     *dsec: 是否存在注解，如果存在就获取
     */
    private Log getAnnotationLog(Method method) {
        if (method != null) {
            return method.getAnnotation(Log.class);
        }
        return null;
    }

    /**
     * desc: 通过反射或缺当前请求方法
     *
     * @param joinPoint 切点
     * @return Method
     */
    private Method getMethod(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method;
        }
        return null;
    }

    /**
     * desc: 处理请求参数
     *
     * @param params
     * @param args
     * @param paramNames
     * @return
     * @throws JsonProcessingException
     */
    private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) throws JsonProcessingException {
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Map) {
                Set set = ((Map) args[i]).keySet();
                List list = new ArrayList();
                List paramList = new ArrayList<>();
                for (Object key : set) {
                    list.add(((Map) args[i]).get(key));
                    paramList.add(key);
                }
                return handleParams(params, list.toArray(), paramList);
            } else {
                if (args[i] instanceof Serializable) {
                    Class<?> aClass = args[i].getClass();
                    try {
                        aClass.getDeclaredMethod("toString", new Class[]{null});
                        // 如果不抛出NoSuchMethodException 异常则存在 toString 方法 ，安全的writeValueAsString ，否则 走 Object的 toString方法
                        params.append("  ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                    } catch (NoSuchMethodException e) {
                        params.append("  ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                    }
                } else if (args[i] instanceof MultipartFile) {
                    MultipartFile file = (MultipartFile) args[i];
                    params.append("  ").append(paramNames.get(i)).append(": ").append(file.getName());
                } else {
                    params.append("  ").append(paramNames.get(i)).append(": ").append(args[i]);
                }
            }
        }
        return params;
    }
}